<?php
/*
 * Copyright (c) 2013 Eugene Kuzmenko
 *
 * Permission  is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in  the Software without restriction, including without limitation the rights
 * to  use,  copy,  modify,  merge, publish, distribute, sublicense, and/or sell
 * copies  of  the  Software,  and  to  permit  persons  to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The  above  copyright  notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 *
 * THE  SOFTWARE  IS  PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED,  INCLUDING  BUT  NOT  LIMITED  TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS  FOR  A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS  OR  COPYRIGHT  HOLDERS  BE  LIABLE  FOR  ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
 * SOFTWARE.
 *
 * For more information, see <http://www.aljey.com>.
 */

namespace Al\Style\Lib\Form;

use Al\Core\Lib\ParameterHolder,
    Al\Core\Lib\Service,
    Doctrine\Common\Util\Inflector,
    Al\Style\Lib\Form\Element\File;

/**
 * The form builder class.
 *
 * @author Eugene Kuzmenko <mail@aljey.com>
 */
class Form extends ParameterHolder {

  /**
   * @var string One of the possible values for $layout.
   */
  static $BASIC = 'basic';

  /**
   * @var string One of the possible values for $layout.
   */
  static $HORIZONTAL = 'horizontal';

  /**
   * @var string One of the possible values for $layout.
   */
  static $INLINE = 'inline';

  /**
   * @var string Form submit method.
   */
  protected $method = 'post';

  /**
   * @var string Form layout: basic, horizontal or inline
   */
  protected $layout = 'horizontal';

  /**
   * @var string A route name.
   */
  protected $route;

  /**
   * @var Object Associated entity object.
   */
  public $entity;

  /**
   * @var File[] All file upload components of a form.
   */
  protected $files = array();

  /**
   * @var array A configuration array to apply to every field.
   */
  protected $config = array();

  /**
   * @var array A collection of Button objects.
   */
  protected $buttons = array(array());

  /**
   * @var string The form id.
   */
  public $id;

  /**
   * @param Object $entity [optional] An entity instance.
   */
  public function __construct($entity = null) {
    $this->id = Service::get('http.post.form_id', uniqid('form-'));
    if ($entity) {
      $this->entity = $entity;
    } else {
      $entity = $this->entity;
      if ($entity) {
        $entity = Service::get('em')->entityLongClass($entity);
        $this->entity = new $entity;
      }
    }
    foreach ($this as $key => $value) {
      $this[$key] = $this->createItem($key, $this->config + $value);
    }
    if (count($this->buttons)) {
      $type = Service::get('config.plugins.Al\Style.element.field');
      foreach ($this->buttons as $key => $value) {
        $this->buttons[$key] = array(
          'type' => 'button',
        ) + $value;
      }
      $this->buttons = new $type($this->config + array(
        'items' => $this->buttons,
      ), $this);
    }
    parent::__construct($this->data);
  }

  /**
   * Returns the currently configured layout.
   *
   * @return string
   */
  public function getLayout() {
    return $this->layout;
  }

  /**
   * Sets a layout.
   *
   * @param $layout
   */
  public function setLayout($layout) {
    $this->layout = $layout;
  }

  /**
   * Returns the form action path.
   *
   * @return string
   */
  public function getAction() {
    return Service::get("config.routes.$this->route")->buildPath($this
      ->getRouteParams());
  }

  /**
   * Returns the route parameters.
   *
   * @return array
   */
  protected function getRouteParams() { return array(); }

  /**
   * Returns the form encoding type.
   *
   * @return string
   */
  private function getEnctype() {
    foreach ($this->files as $file) {
      if (!$file->rich) return 'multipart/form-data';
    }
    return 'application/x-www-form-urlencoded';
  }

  /**
   * Creates a form item, wrapping it in a field if necessary.
   *
   * @param string $name The form field name, as submitted to the server.
   * @param array $params A configuration array.
   * @return Field
   */
  private function createItem($name, $params = array()) {
    $config = array('type' => 'text', 'items' => array());
    Service::mergeRecursive($config, $params);
    if (!($type = Service::get(
        "config.plugins.Al\\Style.element.{$config['type']}"))) {
      throw new \RuntimeException("Unknown form element {$config['type']}");
    }
    if ('field' !== $config['type']) {
      $type = Service::get('config.plugins.Al\Style.element.field');
      $config['items'][$name] = $config;
    }
    $field = new $type($config, $this);
    foreach ($field as $item) {
      if ($item instanceof File) $this->files[] = $item;
    }
    return $field;
  }

  public function getRecord() {
    $service = Service::get();
    $em = $service['em'];
    $props = $em->getPublicProperties($this->entity);
    $meths = $em->getSetters($this->entity);
    foreach ($service['http'][$this->method] as $key => $value) {
      if (in_array($key, $props, true)) {
        $this->entity->$key = $value;
      } elseif (in_array($key = 'set' . Inflector::classify($key), $meths,
          true)) {
        $this->entity->$key($value);
      }
    }
    return $this->entity;
  }

  /**
   * Returns a string representation of the form.
   *
   * @return string
   */
  public function __toString() {
    $limit = false;
    $form_id = false;
    foreach ($this->files as $file) {
      if ($file->rich) {
        if (!$form_id) $form_id = $this->id;
      } elseif ($file->limit && (!$limit || $limit > $file->limit)) {
        $limit = $file->limit;
      }
    }
    $result = Service::get('template')->render(
      Service::get('config.plugins.Al\Style.form.form'), array(
        'limit' => $limit,
        'form_id' => $form_id,
        'layout' => $this->layout,
        'action' => $this->getAction(),
        'method' => $this->method,
        'enctype' => $this->getEnctype(),
        'items' => $this,
        'buttons' => $this->buttons,
      ));
    return $result[0];
  }

  /**
   * Persists the entity to the database.
   *
   * @return $this
   */
  public function save() {
    $em = Service::get('em');
    $em->persist($this->entity);
    $em->flush();
    return $this;
  }

} 